<script setup>
import dayjs from 'dayjs'
import isLeapYear from 'dayjs/plugin/isLeapYear'
import { useElementSize } from '@vueuse/core'
import { mergeDeep } from '@/utils'
dayjs.extend(isLeapYear)

const props = defineProps({
  columns: {
    type: Array,
    default: () => [],
  },
  list: {
    type: Array,
    default: () => [],
  },
  options: {
    type: Object,
    default: () => ({}),
  },
  defaultExpandAll: {
    type: Boolean,
    default: false,
  },
})

const COLUMN_WIDTH = 60
const ROW_HEIGHT = 32
const ITEM_WIDTH = 45
const HEIGHT = 500
const TYPE = 'day'

const defaultOptions = reactive(
  mergeDeep(
    {
      height: HEIGHT,
      rowHeight: ROW_HEIGHT,
      columnWidth: COLUMN_WIDTH,
      itemWidth: ITEM_WIDTH,
      expandAll: false,
      startTime: '',
      endTime: '',
      // year | month | day | hour
      type: TYPE,
      props: {
        id: 'id',
        children: 'children',
      },
    },
    props.options
  )
)

const { itemWidth } = toRefs(defaultOptions)

const columns = computed(() => {
  return props.columns
    .filter((i) => i.show || i.show === undefined)
    .map((col) => {
      if (isEmpty(col.key) && !col.render && !col.type) {
        log('warn', `Key must have a value in columns`)
      }
      col.width = col.width || COLUMN_WIDTH
      const style = col.style
        ? {
            ...col.style,
            width: `${col.width}px`,
          }
        : {
            width: `${col.width}px`,
          }
      return {
        ...col,
        style,
      }
    })
})

const rootIndex = ref(0)

const sourceMap = new Map()

const source = ref([])

watchEffect(() => {
  // 表格内禁用右键
  if (document.getElementsByClassName('project-gantt')[0]) {
    document.getElementsByClassName('project-gantt')[0].oncontextmenu = function () {
      return false
    }
  }
  rootIndex.value = 0
  source.value = buildTree(props.list)
})

// const source = computed(() => {
//   rootIndex.value = 0
//   return buildTree(props.list)
// })

const symbolExtend = Symbol('symbolExtend')
const symbolIndex = Symbol('symbolIndex')
const symbolLevel = Symbol('symbolIndex')
provide('symbolExtend', symbolExtend)
provide('symbolIndex', symbolIndex)
provide('symbolLevel', symbolLevel)

function buildTree(arr, level = 1) {
  return arr.map((i) => {
    let node
    if (sourceMap.has(i[defaultOptions.props.id])) {
      node = sourceMap.get(i[defaultOptions.props.id])
      node.data = i
      node.children = buildTree(i[defaultOptions.props.children] || [], level + 1)
    } else {
      node = {
        id: i[defaultOptions.props.id],
        [symbolLevel]: level,
        [symbolIndex]: ++rootIndex.value,
        [symbolExtend]: defaultOptions.expandAll,
        data: i,
        children: buildTree(i[defaultOptions.props.children] || [], level + 1),
        extend(v) {
          if (v !== undefined) {
            this[symbolExtend] = v
          } else {
            this[symbolExtend] = !this[symbolExtend]
          }
        },
      }
    }
    sourceMap.set(i[defaultOptions.props.id], node)
    // 是否默认展开
    node[symbolExtend] = props.defaultExpandAll
    return node
  })
}

/**
 * @type {ComputedRef<string>}
 */
const startTime = computed(() => {
  return (
    defaultOptions.startTime ||
    source.value.reduce((result, item) => {
      if (!result) return item.data.startTime
      return dayjs(result).diff(item.data.startTime, 'day') >= 0 ? item.data.startTime : result
    }, null)
  )
})

/**
 * @type {ComputedRef<string>}
 */
const endTime = computed(() => {
  return (
    defaultOptions.endTime ||
    source.value.reduce((result, item) => {
      if (!result) return item.data.endTime
      return dayjs(result).diff(item.data.endTime, 'day') >= 0 ? result : item.data.endTime
    }, null)
  )
})

const date = ref([])

const rowItemTotal = ref(0)

watchEffect(() => {
  date.value = createYear(startTime.value, endTime.value)
  rowItemTotal.value = date.value
    .map((y) => {
      return y.month.map((m) => {
        return m.day.map((d) => {
          return d.hour
        })
      })
    })
    .flat(3).length
})

function createYear(start, end) {
  const years = []
  const startObj = dayjs(start)
  const endObj = dayjs(end)
  const diff = endObj.year() - startObj.year()
  for (let i = 0; i <= diff; i++) {
    const year = startObj.add(i, 'year')
    const monthStart = i === 0 ? startObj : i === diff ? endObj.startOf('year') : year.startOf('year')
    const monthEnd = i === diff ? endObj : i === 0 ? startObj.endOf('year') : year.endOf('year')
    years.push({
      date: year.format('YYYY'),
      month: createMonth(monthStart.format('YYYY-MM-DD'), monthEnd.format('YYYY-MM-DD')),
    })
  }
  return years
}

function createMonth(start, end) {
  const months = []
  const startObj = dayjs(start)
  const endObj = dayjs(end)
  const diff = endObj.month() - startObj.month()
  for (let i = 0; i <= diff; i++) {
    const month = startObj.add(i, 'month')
    const dayStart = i === 0 ? startObj : i === diff ? endObj.startOf('month') : month.startOf('month')
    const dayEnd = i === diff ? endObj : i === 0 ? startObj.endOf('month') : month.endOf('month')
    months.push({
      date: month.format('MM'),
      day: createDay(dayStart.format('YYYY-MM-DD'), dayEnd.format('YYYY-MM-DD')),
    })
  }
  return months
}

function createDay(start, end) {
  const days = []
  const startObj = dayjs(start)
  const endObj = dayjs(end)
  const diff = endObj.date() - startObj.date()
  for (let i = 0; i <= diff; i++) {
    const day = startObj.add(i, 'day')
    days.push({
      date: day.format('DD'),
      // hour: ['上午', '下午'],
    })
  }
  return days
}

const bodyWidth = computed(() => {
  const ganttWidth = rowItemTotal.value * itemWidth.value
  const tableWidth = props.columns
    .filter((i) => i.show || i.show === undefined)
    .reduce((result, item) => {
      result += item.width
      return result
    }, 0)
  return ganttWidth + tableWidth
})

function log(type, text) {
  console[type](text)
}

function isEmpty(prop) {
  return prop === undefined || prop === null || prop === ''
}

const headRef = ref()

function handleScroll(e) {
  headRef.value.scrollLeft = e.target.scrollLeft
}

const { height: headHeight } = useElementSize(headRef)

const bodyHeight = computed(() => {
  if (headRef.value) {
    if (typeof defaultOptions.height === 'string') {
      return `calc(${defaultOptions.height} - ${headHeight.value + 12}px)`
    } else {
      return `${defaultOptions.height - headHeight}px`
    }
  } else return 0
})

const ganttHeight = computed(() => {
  if (typeof defaultOptions.height === 'string') {
    return defaultOptions.height
  } else {
    return `${defaultOptions.height}px`
  }
})

const ganttRef = ref()

watch(date, (v) => {
  const { width } = ganttRef.value.getBoundingClientRect()
  if (width > bodyWidth.value) {
    const gap = Math.ceil((width - bodyWidth.value) / itemWidth.value)
    const lastYear = v.length && v[v.length - 1]
    const lastMonth = lastYear && lastYear.month.length && lastYear.month[lastYear.month.length - 1]
    const lastDay = lastMonth && lastMonth.day.length && lastMonth.day[lastMonth.day.length - 1]
    if (lastDay) {
      const lastDayObj = dayjs(`${lastYear.date}-${lastMonth.date}-${lastDay}`)
      lastMonth.day.push(...createDay(lastDayObj.format('YYYY-MM-DD'), lastDayObj.add(gap, 'day').format('YYYY-MM-DD')))
    }
  }
})

provide('rowItemTotal', rowItemTotal)
provide('defaultOptions', defaultOptions)
provide('startTime', startTime)
provide('endTime', endTime)
</script>

<template>
  <div ref="ganttRef" class="gantt-chart" :style="{ height: ganttHeight }">
    <div ref="headRef" class="gantt-chart-head">
      <div class="gantt-chart-table-hand">
        <div class="gantt-chart-table-hand-cell" v-for="(col, colIndex) in columns" :key="colIndex" :style="col.style">
          {{ col.label }}
        </div>
      </div>
      <div class="gantt-chart-calendar-hand">
        <div class="calendar-row">
          <div class="calendar-year" v-for="(year, yearIndex) in date" :key="yearIndex">
            <!-- <div class="year-row">{{ year.date }}</div> -->
            <div class="calendar-row">
              <div class="calendar-month" v-for="(month, monthIndex) in year.month" :key="monthIndex">
                <div class="month-row">{{ year.date }}年{{ month.date }}月</div>
                <div class="calendar-row">
                  <div class="calendar-day" v-for="(day, dayIndex) in month.day" :key="dayIndex">
                    <div class="day-row" :style="{ width: `${ITEM_WIDTH}px` }">{{ day.date }}</div>
                    <!-- <div class="calendar-row">
                      <div
                        class="calendar-hour"
                        v-for="(hour, hourIndex) in day.hour"
                        :key="hourIndex"
                        :style="{ minWidth: `${itemWidth}px` }">
                        <div class="hour-row">{{ hour }}</div>
                      </div>
                    </div> -->
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
    <div class="gantt-chart-body" @scroll="handleScroll" :style="{ maxHeight: bodyHeight }">
      <div class="gantt-chart-scroll" :style="{ width: `${bodyWidth}px` }">
        <template v-if="source.length">
          <Row v-for="item in source" :key="item.id" :columns="columns" :node="item"></Row>
        </template>
        <span v-else class="body-empty">暂无数据</span>
      </div>
    </div>
  </div>
</template>

<style scoped lang="scss">
.gantt-chart {
  position: relative;
  width: 100%;
  background-color: var(--elp-bg-color);
  .gantt-chart-head {
    display: flex;
    margin-bottom: 5px;
    overflow: auto;
    border-bottom: var(--elp-border);
    border-top: var(--elp-border);
    border-right: var(--elp-border);
    &::-webkit-scrollbar {
      height: 0;
      width: 0;
    }
    .gantt-chart-table-hand {
      position: sticky;
      left: 0;
      top: 0;
      z-index: 999;
      display: flex;
      background-color: var(--elp-bg-color);
      border-left: var(--elp-border);
      .gantt-chart-table-hand-cell {
        display: flex;
        align-items: center;
        padding: 5px;
        border-right: var(--elp-border);
      }
    }
    .gantt-chart-calendar-hand {
      .calendar-row {
        display: flex;
      }
      .calendar-year {
        text-align: center;
        .year-row {
          border-right: var(--elp-border);
        }
      }
      .calendar-month {
        text-align: center;
        .month-row {
          height: 30px;
          line-height: 30px;
          border-right: var(--elp-border);
        }
      }
      .calendar-day {
        text-align: center;
        .day-row {
          height: 30px;
          line-height: 30px;
          border-right: var(--elp-border);
        }
      }
      .calendar-hour {
        width: 50%;
        text-align: center;
        .hour-row {
          border-right: var(--elp-border);
        }
      }
      .calendar-year {
        &:last-child {
          .year-row {
            border-right: none;
          }
          .calendar-month {
            &:last-child {
              .month-row {
                border-right: none;
              }
              .calendar-day {
                &:last-child {
                  .day-row {
                    border-right: none;
                  }
                  .calendar-hour:last-child {
                    .hour-row {
                      border-right: none;
                    }
                  }
                }
              }
            }
          }
        }
      }
      .day-row,
      .month-row,
      .year-row {
        border-bottom: var(--elp-border);
      }
    }
  }
  .gantt-chart-body {
    position: relative;
    overflow: auto;
    border: var(--elp-border);
    .gantt-chart-scroll {
      min-height: 100px;
    }
    .body-empty {
      position: sticky;
      left: 50%;
      top: 50%;
    }
  }
}
</style>
